/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Hmfs-tools io
 */

#include "hmfs_io.h"

#include <algorithm>
#include <cassert>
#include <cerrno>
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fcntl.h>
#include <mntent.h>
#include <securec.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <unistd.h>

#include "device_manager.h"
#include "hmfs_log.h"

namespace OHOS {
namespace Hmfs {
static int32_t GetDeviceFd(uint64_t *offset)
{
    HMFS_CHECK(offset != nullptr, return -1, "Offset is null");
    uint64_t blockAddr = *offset >> HMFS_BLKSIZE_BITS;
    for (uint32_t i = 0; i < DeviceManager::GetInstance().GetDeviceCount(); i++) {
        DeviceInfo *deviceInfo = DeviceManager::GetInstance().GetDeviceInfo(i);
        if (deviceInfo == nullptr) {
            HMFS_DEBUG("failed to get device info by id %u", i);
            continue;
        }

        if ((deviceInfo->startBlockId <= blockAddr) && (deviceInfo->endBlockId >= blockAddr)) {
            *offset -= deviceInfo->startBlockId << HMFS_BLKSIZE_BITS;
            return deviceInfo->fd;
        }
    }

    return -1;
}

Dcache &Dcache::GetInstance()
{
    static Dcache instance;
    return instance;
}

void Dcache::DcacheRelease()
{
    if (!dcacheInitialized_) {
        return;
    }

    dcacheInitialized_ = false;

    if (dcacheBlk_ != nullptr) {
        delete[] dcacheBlk_;
    }
    if (dcacheLastused_ != nullptr) {
        delete[] dcacheLastused_;
    }
    if (dcacheBuf_ != nullptr) {
        delete[] dcacheBuf_;
    }
    if (dcacheValid_ != nullptr) {
        delete[] dcacheValid_;
    }

    dcacheConfig_.cacheEntryCount = 0;
    dcacheBlk_ = nullptr;
    dcacheLastused_ = nullptr;
    dcacheBuf_ = nullptr;
    dcacheValid_ = nullptr;
}

int32_t Dcache::DcacheAllocAll(long num)
{
    if (num <= 0) {
        return -1;
    }
    if ((dcacheBlk_ = new off64_t[num]) == nullptr ||
        (dcacheLastused_ = new uint64_t[num]) == nullptr ||
        (dcacheBuf_ = new char[HMFS_BLOCK_SIZE * num]) == nullptr ||
        (dcacheValid_ = new bool[num]) == nullptr) {
        DcacheRelease();
        return -1;
    }
    dcacheConfig_.cacheEntryCount = num;
    return 0;
}

void Dcache::DcacheRelocateInit(void)
{
    uint32_t offset0ArraySize = (sizeof(dcacheRelocateOffset0_) / sizeof(dcacheRelocateOffset0_[0]));
    uint32_t offsetArraySize = (sizeof(dcacheRelocateOffset_) / sizeof(dcacheRelocateOffset_[0]));
    if (offset0ArraySize != offsetArraySize) {
        HMFS_DEBUG("dcacheRelocateOffset0_ and dcacheRelocateOffset_ size mismatch\n");
    }

    for (uint32_t i = 0; i < offsetArraySize && i < dcacheConfig_.maxCollisionCount; i++) {
        if (labs(dcacheRelocateOffset0_[i]) > dcacheConfig_.cacheEntryCount / NUM_DIVISOR) {
            dcacheConfig_.maxCollisionCount = i;
            break;
        }
        dcacheRelocateOffset_[i] = dcacheConfig_.cacheEntryCount + dcacheRelocateOffset0_[i];
    }
}

void Dcache::DcacheInit(void)
{
    if (dcacheConfig_.cacheEntryCount <= 0) {
        return;
    }
    DcacheRelease();

    long maxCount = std::max(MIN_NUM_CACHE_ENTRY, dcacheConfig_.cacheEntryCount);
    Dcache &instance = Dcache::GetInstance();

    while (instance.DcacheAllocAll(maxCount) != 0 && maxCount != MIN_NUM_CACHE_ENTRY) {
        maxCount = std::max(MIN_NUM_CACHE_ENTRY, maxCount / NUM_DIVISOR);
    }

    instance.DcacheRelocateInit();
    dcacheInitialized_ = true;

    if (!dcacheExitRegistered_) {
        dcacheExitRegistered_ = true;
        Dcache::DcacheRelease();
    }

    dcacheReadAccess_ = 0;
    dcacheReadHit_ = 0;
    dcacheReadMiss_ = 0;
    dcacheReadReplace_ = 0;
}

inline char *Dcache::DcacheAddr(long entry)
{
    return dcacheBuf_ + HMFS_BLOCK_SIZE * entry;
}

inline long Dcache::DcacheRelocate(long entry, int32_t index)
{
    return (entry + dcacheRelocateOffset_[index]) % dcacheConfig_.cacheEntryCount;
}

long Dcache::DcacheFind(off64_t block)
{
    long min = dcacheConfig_.cacheEntryCount;
    unsigned max = dcacheConfig_.maxCollisionCount;
    long entry = 0;
    long leastUsed = 0;
    long target = 0;
    unsigned index = 0;

    if (min <= 0) {
        return 0;
    }
    target = leastUsed = entry = block % min;

    for (index = 0; index < max; index++) {
        if (!dcacheValid_[target] || dcacheBlk_[target] == block) {
            return target;
        }
        if (dcacheLastused_[target] < dcacheLastused_[leastUsed]) {
            leastUsed = target;
        }
        target = DcacheRelocate(entry, index);
    }
    return leastUsed;
}

int32_t Dcache::DcacheIoRead(int32_t fd, long entry, off64_t offset, off64_t block)
{
    if (pread64(fd, dcacheBuf_ + entry * HMFS_BLOCK_SIZE, HMFS_BLOCK_SIZE, offset) < 0) {
        HMFS_DEBUG("Failed to read offset = 0x%" PRIx64 "", offset);
        return -1;
    }
    dcacheLastused_[entry] = ++dcacheUsetick_;
    dcacheValid_[entry] = true;
    dcacheBlk_[entry] = block;
    return 0;
}

int32_t Dcache::DcacheUpdateRwPart(int32_t fd, long entry, off64_t start, off64_t block, bool isWrite)
{
    int ret = 0;
    if (!isWrite) {
        ++dcacheReadMiss_;
        if (dcacheValid_[entry]) {
            ++dcacheReadReplace_;
        }
        ret = DcacheIoRead(fd, entry, start, block);
    }
    return ret;
}

int32_t Dcache::DcacheUpdateRw(int32_t fd, void *buf, off64_t offset, size_t byteCount, bool isWrite)
{
    HMFS_CHECK(buf != nullptr, return -1, "Buf is nullptr");
    int32_t ret = 0;
    if (!dcacheInitialized_) {
        DcacheInit();
    }
    if (!dcacheInitialized_) {
        return 1;
    }

    off64_t block = offset / HMFS_BLOCK_SIZE;
    int32_t addrInBlock = offset % HMFS_BLOCK_SIZE;
    off64_t start = block * HMFS_BLOCK_SIZE;

    while (byteCount != 0) {
        size_t curSize = std::min(byteCount, static_cast<size_t>(HMFS_BLOCK_SIZE - addrInBlock));
        long entry = DcacheFind(block);
        if (!isWrite) {
            ++dcacheReadAccess_;
        }

        if (dcacheValid_[entry] && dcacheBlk_[entry] == block) {
            if (isWrite) {
                ret = memcpy_s(DcacheAddr(entry) + addrInBlock, curSize, buf, curSize);
                HMFS_CHECK(ret == 0, return -1, "Failed to copy cache");
            } else {
                ++dcacheReadHit_;
            }
        } else {
            ret = DcacheUpdateRwPart(fd, entry, start, block, isWrite);
            if (ret != 0) {
                return ret;
            }
        }

        if (!isWrite) {
            ret = memcpy_s(buf, curSize, DcacheAddr(entry) + addrInBlock, curSize);
            HMFS_CHECK(ret == 0, return -1, "Failed to copy cache");
        }

        ++block;
        buf = reinterpret_cast<uint8_t *>(buf) + curSize;
        start += HMFS_BLOCK_SIZE;
        byteCount -= curSize;
        addrInBlock = 0;
    }
    return ret;
}

int32_t Dcache::DcacheUpdateCache(int32_t fd, void *buf, off64_t offset, size_t count)
{
    return DcacheUpdateRw(fd, buf, offset, count, true);
}

int32_t Dcache::DcacheRead(int32_t fd, void *buf, off64_t offset, size_t count)
{
    return DcacheUpdateRw(fd, buf, offset, count, false);
}

HmfsIo &HmfsIo::GetInstance()
{
    static HmfsIo instance;
    return instance;
}

int32_t HmfsIo::DevRead(void *buf, uint64_t offset, size_t len)
{
    HMFS_CHECK(buf != nullptr, return -1, "Buf is nullptr");

    int32_t fd = GetDeviceFd(&offset);
    if (fd < 0) {
        return fd;
    }

    int err = Dcache::GetInstance().DcacheRead(fd, buf, static_cast<off64_t>(offset), len);
    if (err <= 0) {
        return err;
    }
    if (pread64(fd, buf, len, offset) < 0) {
        return -1;
    }
    return 0;
}

int32_t HmfsIo::DevReadAhead(uint64_t offset)
{
    int32_t fd = GetDeviceFd(&offset);
    if (fd < 0) {
        return fd;
    }
    return 0;
}

int32_t HmfsIo::DevWrite(void *buf, uint64_t offset, size_t len)
{
    HMFS_CHECK(buf != nullptr, return -1, "Buf is nullptr");

    int32_t fd = GetDeviceFd(&offset);
    if (fd < 0) {
        HMFS_DEBUG("failed to GetDeviceFd offset = %" PRIu64 "", offset);
        return fd;
    }

    if (Dcache::GetInstance().DcacheUpdateCache(fd, buf, static_cast<off64_t>(offset), len) < 0) {
        HMFS_DEBUG("failed to DcacheUpdateCache");
        return -1;
    }

    if (pwrite64(fd, buf, len, offset) < 0) {
        HMFS_DEBUG("failed to pwrite64 offset = %" PRIu64 ", len = %zu, buf = %p, error = %s",
            offset, len, buf, strerror(errno));
        return -1;
    }
    return 0;
}

int32_t HmfsIo::DevWriteBlock(void *buf, uint64_t blockAddr)
{
    return DevWrite(buf, blockAddr << HMFS_BLKSIZE_BITS, HMFS_BLOCK_SIZE);
}

int32_t HmfsIo::DevFill(void *buf, uint64_t offset, size_t len)
{
    HMFS_CHECK(buf != nullptr, return -1, "Buf is nullptr");

    int32_t fd = GetDeviceFd(&offset);
    if (fd < 0) {
        return fd;
    }

    if (*(reinterpret_cast<uint8_t *>(buf))) {
        return -1;
    }

    if (pwrite64(fd, buf, len, offset) < 0) {
        return -1;
    }
    return 0;
}

int32_t HmfsIo::DevFillBlock(void *buf, uint64_t blockAddr)
{
    return DevFill(buf, blockAddr << HMFS_BLKSIZE_BITS, HMFS_BLOCK_SIZE);
}

int32_t HmfsIo::DevReadBlock(void *buf, uint64_t blockAddr)
{
    return DevRead(buf, blockAddr << HMFS_BLKSIZE_BITS, HMFS_BLOCK_SIZE);
}

int32_t HmfsIo::DevReadaBlock(uint64_t blockAddr)
{
    return DevReadAhead(blockAddr << HMFS_BLKSIZE_BITS);
}

int32_t HmfsIo::HmfsFsyncDevice()
{
    for (uint32_t id = 0; id < DeviceManager::GetInstance().GetDeviceCount(); id++) {
        DeviceInfo *deviceInfo = DeviceManager::GetInstance().GetDeviceInfo(id);
        if (deviceInfo != nullptr) {
            if (fsync(deviceInfo->fd) < 0) {
                HMFS_DEBUG("Failed to do fsync, errno %d", errno);
                return -1;
            }
        }
    }
    return 0;
}

int32_t HmfsIo::HmfsFinalizeDevice()
{
    int32_t ret = 0;
    for (uint32_t id = 0; id < DeviceManager::GetInstance().GetDeviceCount(); id++) {
        DeviceInfo *device = DeviceManager::GetInstance().GetDeviceInfo(id);
        if (device == nullptr) {
            continue;
        }

        ret = fsync(device->fd);
        if (ret < 0) {
            HMFS_DEBUG("Could not conduct fsync.");
            break;
        }
        ret = close(device->fd);
        if (ret < 0) {
            HMFS_DEBUG("Failed to close device file.");
            break;
        }
        device->fd = -1;
    }

    return ret;
}
} // namespace Hmfs
} // namespace OHOS